

import inspect
import pygments
import re
#~ from Questions_tpl.variables_and_conditions import  *

from pygments.lexers import PythonLexer, CppLexer
pylexer = lexer = PythonLexer(ensurenl=False)
cpplexer = CppLexer(ensurenl=False)


def adapt_code_spacing(code, unneeded='', needed_once=' ', ignore_space_after_escape_chars=True, lang='py' ):  # delete unneeded spaces, without changing the meaning
    """
    a  = 5 
    while a> 2: 
        if  a != 3:
            print(a,"Hello World" )
        a -= 1
    """
    #~ print(  list( lexer.get_tokens( code ) ) )
    #~ print ( "**********************"); print();

    rez = ''
    prevtype=None
    prevval=None

    if lang=='cpp': lexer = cpplexer
    else: lexer = pylexer
        
    for t in lexer.get_tokens( code )  :
        #~ print(t)
        ttype, tval = t
        if prevtype:            
            if str(ttype)=='Token.Text'  and tval.strip(' \t')=='':    # is space
                    if str(prevtype) == 'Token.Keyword': 
                        tval = needed_once
                    elif not prevval.endswith('\n'):
                        tval = unneeded
            elif not( str(prevtype)=='Token.Text'  and prevval.strip(' \t')=='') : # was not space
                #~ if not re.search(ignore_whenprev, prevval):
                if not prevval.endswith( '\\' ) or not ignore_space_after_escape_chars:
                    rez += unneeded
        prevtype, prevval = t; 

        rez += tval
        
        #~ print(tval, end='')
    return rez.rstrip('\n')

def regexpize_code_spacing(code, lang='py'):
    return adapt_code_spacing(code, unneeded=' *', needed_once=' +', lang=lang)
     
def minimize_code_spacing(code, lang='py'):
    return adapt_code_spacing(code, lang=lang)


def split_off_comment(line, lang='py'):
    rez_line = ''
    
    if lang=='cpp': lexer = cpplexer
    else: lexer = pylexer
    #~ print("##", line)
    prev_tval = ''

    for ttype, tval in lexer.get_tokens( line ):
        #~ print('##', ttype, tval)
        if lang=='cpp' and str(ttype) == 'Token.Operator' and tval=='/' and prev_tval=='/':
            return rez_line[:-1], line[len(rez_line)-1:]
        elif str(ttype) == 'Token.Comment':
            return rez_line, tval
        else:
            rez_line += tval
        prev_tval = tval
    return line, None

def prepair_gaps(code, SHOWERRORS=True, lang='py'):
    gaps = []
    lines = []
    for lnr, line in enumerate(code.splitlines() ):
        line, comment = split_off_comment(line, lang=lang)
        
        if comment  and  ('HIDE' in comment):
            comment_beginning, *search_patterns = comment.split('HIDE')
            cb = comment_beginning.rstrip(' #\t')
            if lang=='cpp' and cb.endswith('//'): cb=cb.rstrip('/'); 
            line = line + cb
            
            for pattern in search_patterns:
                
                pattern, *alternatives = re.split (r"(?<!\\)\~", pattern)  # moodle GIFT style alternatives are separated with ~
                                                                           # alternatives are good for feedback for "near miss" answers
                                                                           # if you use ~ literraly, must escape it with \~
                                                                           # for GIFT, one should escape \}#~/"'
                alternatives = '~'.join(alternatives) # join back alternatives to string :)
                
                EXACT =  pattern.startswith('EXACT')
                REGEXP =  pattern.startswith('REGEXP')
                if EXACT: pattern = pattern[len('EXACT'):] #trim 'EXACT'
                if REGEXP: pattern = pattern[len('REGEXP'):] #trim 'REGEXP'
                
                pattern = pattern.strip()
                if pattern.startswith('(') and pattern.endswith(')'):
                    pattern = pattern[1:-1]

                pattern = pattern.strip() # strip spaces -- as they are ignored by Moodle short answer anyway
                pattern = pattern.replace('\\~', '~')  # unescape \~ 

                answer = None
                if EXACT:
                    if pattern in line:
                        answer = pattern
                elif REGEXP:
                    regexp_match = re.search(pattern, line)
                    if regexp_match:
                        answer = regexp_match.group(0)                    
                else:
                    for s in '\\.+*?^$!(){,}<>=:|':  # escape regexp spec. chars 
                        pattern = pattern.replace(s, '\\'+s)
                    regexp = regexpize_code_spacing(pattern, lang=lang)
                    #~ print('pattern regexp:', regexp)
                    regexp_match = re.search(regexp, line)
                    if regexp_match:
                        answer = regexp_match.group(0)
                        
                if answer:
                    line =  line.replace( answer, '..%s....' % (len(gaps)), 1)
                    #~ print('ANSW', lnr, (answer,))
                    if not EXACT:
                        answer = minimize_code_spacing(answer, lang=lang) 
                    if alternatives:
                        gaps.append( (lnr, [answer, alternatives] ) )        
                    else:
                        gaps.append( (lnr, answer) )
                elif SHOWERRORS:
                    print("<!-- ANSWER for pattern '%s' NOT FOUND on line nr. %s: %s -->" % (pattern, lnr, line))
        #~ print(line)
        lines.append(line)

    return ('\n'.join(lines), gaps)



if __name__ == '__main__':

    #~ code = inspect.getsource(test_gap_with_alternatives)
    #~ code = inspect.getsource(test_gap_with_baseword)
    from Questions_tpl.fill_missing_tests import *
    code = inspect.getsource(test_gap_with_spec_chars_HIDEEXACT_tilde_and_backslash)
    #~ code = inspect.getsource(test_gap_HIDEREGEXP)
    for x in  prepair_gaps(code):
        print(x)
